package jgf;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import jgf.exceptions.attribute.AttributeException;
import jgf.exceptions.attribute.DuplicatedAttributeException;
import jgf.exceptions.attribute.UnexistentAttributeException;

/**
 * This class implements the funcionality of the arc of a graph.
 * @author Marcos Garcia Ortega <a href="mailto:thiamath@gmail.com">&lt;thiamath@gmail.com&gt;</a>
 */
public class Arc implements Serializable {

	////////////
	// Fields //
	////////////

	// <editor-fold defaultstate="collapsed" desc="Fields">
	/** Identifies univocally this arc on the graph. */
	private Integer id;
	/** Reference to the node from where the arc leaves. */
	private Node from;
	/** Reference to the node where the arc arrives. */
	private Node to;
	/** The label assigned to the arc. */
	protected String label;
	/** A map of the attributes of the arc. */
	protected TreeMap<String, Attribute> attributes;
	// </editor-fold>

	////////////////////
	// Arc Management //
	////////////////////

	// <editor-fold defaultstate="collapsed" desc="Constructors">
	/**
	 * The constructor of an empty and unuseful arc. This constructor
	 * should not be used.
	 */
	protected Arc() {
		this(0, null, null);
	}

	/**
	 * This constructor creates aun arc without label and without attributes.
	 * @param arcID The ID number of this arc, assigned by the owner graph.
	 * @param from The node from where the arc leaves.
	 * @param to The node where the arc arrives.
	 */
	public Arc(Integer arcID, Node from, Node to) {
		this(arcID, from, to, "");
	}

	/**
	 * This constructor creates an arc and assign to it a label.
	 * @param arcID The ID number of this arc, assigned by the owner graph.
	 * @param from The node from where the arc leaves.
	 * @param to The node where the arc arrives.
	 * @param label The label of the arc.
	 */
	public Arc(Integer arcID, Node from, Node to, String label) {
		this(arcID, from, to, label, null);
	}

	/**
	 * This constructor creates an arc and assigns to it a label and a list
	 * of attributes.
	 * @param arcId The ID number of this arc, assigned by the owner graph.
	 * @param from The node from where the arc leaves.
	 * @param to The node where the arc arrives.
	 * @param label The label of the arc.
	 * @param atts The list of attributes of the arc.
	 */
	public Arc(Integer arcId, Node from, Node to, String label, List<Attribute> atts) {
		this.id = arcId;
		this.from = from;
		this.to = to;
		this.label = label;
		attributes = new TreeMap<String, Attribute>();
		if (atts != null) {
			for (Attribute attribute : atts) {
				attributes.put(attribute.getName(), attribute);

			}

		}
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="Getters">
	/**
	 * Gets the ID number of the arc, as given from the graph.
	 * @return An Integer with the ID number of the arc.
	 */
	public Integer getId() {
		return id;
	}

	/**
	 * Returns the node from where the arc leaves.
	 * @return A Node with the 'from' node of the arc.
	 */
	public Node getFrom() {
		return from;
	}

	/**
	 * Returns the node where the node arrives.
	 * @return A Node with the 'to' node of the arc.
	 */
	public Node getTo() {
		return to;
	}

	/**
	 * Returns the label of the arc.
	 * @return A String with the label of the arc.
	 */
	public String getLabel() {
		return label;
	}
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="Setters">
	/**
	 * Sets or replaces the label of the arc.
	 * @param label A String with the new label of the arc.
	 */
	public void setLabel(String label) {
		this.label = label;
	}
	// </editor-fold>

	//////////////////////////
	// Attribute management //
	//////////////////////////

	// <editor-fold defaultstate="collapsed" desc="Attribute management">
	/**
	 * Add a new attribute to the arc, with the name "name" and the value "value".
	 * @param newAttribute The new attribute.
	 */
	public void insertAttribute(Attribute newAttribute) {
		attributes.put(newAttribute.getName(), newAttribute);
	}

	/**
	 * Erase the attribute with name "attributeName" from the arc.
	 * @param attributeName The name of the attribute.
	 */
	public void removeAttribute(String attributeName) {
		attributes.remove(attributeName);
	}

	/**
	 * Changes the name of an arc attribute to a new name and changes also
	 * its default value.
	 * @param previousName A String with the previous name of the attribute.
	 * @param newAttribute An Attribute with the new attribute name and value.
	 * @throws AttributeException
	 * @throws DuplicatedAttributeException If the new attribute already exists
	 *	on the list of default attributes for nodes, this exception will raise.
	 * @throws UnexistentAttributeException If the previous name does not exist
	 *	on the list of default attributes for nodes, this exception will raise.
	 */
	public void updateAttribute(String previousName, Attribute newAttribute)
			throws AttributeException,
			UnexistentAttributeException,
			DuplicatedAttributeException {
		if (previousName == null || newAttribute == null) {//If the previous name or the new attribute are null
			throw new AttributeException(this.getClass(),
					"updateDefaultArcAttribute",
					new String[]{"previousName = " + previousName,
						"newAttribute = " + newAttribute});//Throw a AttributeException
		}
		if (!attributes.containsKey(previousName)) {//If the previous attribute do not exist
			throw new UnexistentAttributeException(this.getClass(),
					"updateDefaultArcAttribute",
					new String[]{"previousName = " + previousName,
						"newName = " + newAttribute});//Throw a UnexistentAttributeException
		}
		if (!previousName.equalsIgnoreCase(newAttribute.getName())) {//If the new name is different than the previous one
			if (attributes.containsKey(newAttribute.getName())) {
				throw new DuplicatedAttributeException(this.getClass(),
						"updateDefaultArcAttribute",
						new String[]{"previousName = " + previousName,
							"newAttribute = " + newAttribute});//Throw a DuplicatedAttributeException
			} else {
				attributes.remove(previousName);//remove the previous attribute
			}
		}
		attributes.put(newAttribute.getName(), newAttribute);//Put or replace the new attribute
	}

	/**
	 * Put replace the list of attributes into the arc.
	 * @param attributes The list of attributes to be included in the arc.
	 */
	public void setAttributeList(List<Attribute> attributes) {
		this.attributes.clear();
		for (Attribute attribute : attributes) {
			this.attributes.put(attribute.getName(), attribute);
		}
	}

	/**
	 * Gets the whole list of attributes of the arc.
	 * @return A List of Attribute with all the attributes of the arc.
	 */
	public List<Attribute> getAttributeList() {
		List<Attribute> ret = new ArrayList<Attribute>();

		for (String key : attributes.keySet()) {
			ret.add(attributes.get(key));
		}

		return ret;
	}

	/**
	 * Returns the attribute with name "name".
	 * @param name The name of the attribute.
	 * @return The selected Attribute.
	 * @throws UnexistentAttributeException If the arc do not contain
	 *	the attribute, this exception will rise.
	 */
	public Attribute getAttribute(String name) throws UnexistentAttributeException {
		Attribute attribute = attributes.get(name);
		if (attribute == null) {
			throw new UnexistentAttributeException(this.getClass(),
					"getAttribute",
					new String[]{"name = " + name});
		}
		return attribute;
	}
	// </editor-fold>

	@Override
	public String toString() {
		return label;
	}
}
